#include <iostream>
using std::cerr;
using std::endl;
#include <cmath>
#include <assert.h>
#include <GL/gl.h>
#include "test_screen.h"
#include "font_related.h"

//todo szybko szybciej jeszcze szybciej... przemysl kod timera
#define FRAME_COUNTER
#define C_frameCounterBucketSize 200

#ifdef FRAME_COUNTER
#include "nat_timer.h"
natFPS G_fpsCounter(C_frameCounterBucketSize);
#endif


int test_screen::keyboard_func(unsigned char key, int x, int y)
{
   switch(key)
   {
      case 27://escape
         return key;
      case 'w':
         if(lods.size() > 0)
         {
                  // vector front is normalized
            user_pos.x -= front.x * STEP_LENGTH;
            user_pos.y -= front.y * STEP_LENGTH;
            user_pos.z -= front.z * STEP_LENGTH;
            select_lods();
         }
         return 0;
      case 's':
         if(lods.size() > 0)
         {
            user_pos.x += front.x * STEP_LENGTH;
            user_pos.y += front.y * STEP_LENGTH;
            user_pos.z += front.z * STEP_LENGTH;
            select_lods();
         }
         return 0;
      case 'm':
         change_polygon_mode();
      case 'l':
         use_lod = !use_lod;
      default:
         return 0;//don't care about unhandled keys
   }
}

//void test_screen::mouse_func(int button, int state, int x, int y)
//{
//}

//void test_screen::mouse_motion_func(int x, int y)
//{
//}

void test_screen::passive_motion_func(int x, int y)
{
   float xchange = x - mouse_prev_x;
   float ychange = y - mouse_prev_y;
   //todo w innych plikach zapomnialem, ze moga wystapic duze ujemne... 
   if(xchange>-15 && xchange<15 && ychange>-15 && ychange<15)
   {
      GLfloat rotm[16];
      GLfloat oldv[4];
      GLfloat newv[4];

      xchange *= ONE_PIXEL_HROT;
      //cerr<<xchange<<endl;

      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      glLoadIdentity();

      glRotatef(xchange, hrotv.x, hrotv.y, hrotv.z);
      glGetFloatv(GL_MODELVIEW_MATRIX, rotm);// the matrix is kept in rows

      // rotate front and vrot vectors
      oldv[0]=front.x;
      oldv[1]=front.y;
      oldv[2]=front.z;
      oldv[3]=1.0f;
      for(int r=0; r<4; r++) {
         newv[r]=0.0f;
         for(int c=0; c<4; c++)
            newv[r] += rotm[r*4+c]*oldv[c];
      }
      front.x = newv[0];
      front.y = newv[1];
      front.z = newv[2];

      oldv[0] = vrotv.x;
      oldv[1] = vrotv.y;
      oldv[2] = vrotv.z;
      oldv[3] = 1.0f;
      for(int r=0; r<4; r++) {
         newv[r]=0.0f;
         for(int c=0; c<4; c++)
            newv[r] += rotm[r*4+c]*oldv[c];
      }
      vrotv.x = newv[0];
      vrotv.y = newv[1];
      vrotv.z = newv[2];

      // horizontal rotation
      hrot += xchange;
      if(hrot>360.0)
         hrot -= 360.0;
      else if(hrot<0.0)
         hrot += 360.0;



      ychange *= ONE_PIXEL_VROT;
      glLoadIdentity();

      glRotatef(ychange, vrotv.x, vrotv.y, vrotv.z);
      glGetFloatv(GL_MODELVIEW_MATRIX, rotm);

      // rotate front and hrot vectors
      oldv[0]=front.x;
      oldv[1]=front.y;
      oldv[2]=front.z;
      oldv[3]=1.0f;
      for(int r=0; r<4; r++) {
         newv[r]=0.0f;
         for(int c=0; c<4; c++)
            newv[r] += rotm[r*4+c]*oldv[c];
      }
      front.x = newv[0];
      front.y = newv[1];
      front.z = newv[2];

      oldv[0] = hrotv.x;
      oldv[1] = hrotv.y;
      oldv[2] = hrotv.z;
      oldv[3] = 1.0f;
      for(int r=0; r<4; r++) {
         newv[r]=0.0f;
         for(int c=0; c<4; c++)
            newv[r] += rotm[r*4+c]*oldv[c];
      }
      hrotv.x = newv[0];
      hrotv.y = newv[1];
      hrotv.z = newv[2];

      // vertical rotation
      vrot += ychange;
      if(vrot>360.0)
         vrot -= 360.0;
      else if(hrot<0.0)
         vrot += 360.0;

      glPopMatrix();
   }
   
   mouse_prev_x = x;
   mouse_prev_y = y;
}


void test_screen::display_func(int width, int height)
{
   //PushOrtho(width, height); 
   //DrawString("eczane",10,30);
   //PopOrtho();

   if(lods.size()<=0 || positions.size()<=0)
      return;
   
   
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   glLoadIdentity();

   /*light.disable();
   glPushMatrix();
   glTranslatef(0.f,0.f,-3.0f);
   glColor3f(0.4,0.8,0.8);
   glutSolidCube(0.3);
   glPopMatrix();*/
   
   /*light.disable();
   glPushMatrix();
   glTranslatef(-user_pos.x, -user_pos.y, -user_pos.z);
   glColor3f(0.9,0.1,0.1);
   glBegin(GL_LINES);
      glVertex3f(0.0,0.0,0.0);
      glVertex3f(front.x, front.y, front.z);
   glEnd();
   glTranslatef(front.x, front.y, front.z);
   glutWireCube(0.5);
   glPopMatrix();*/

   light.enable();
   {
      GLfloat col[4];
      col[0]=0.8;
      col[1]=0.8;
      col[2]=0.8;
      col[3]=1.0;
      glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,col);
      col[0]=0.0;
      col[1]=0.0;
      col[2]=0.0;
      col[3]=1.0;
      glMaterialfv(GL_FRONT_AND_BACK,GL_EMISSION,col);
      glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,50.0);
   }

   unsigned pend = positions.size();
   for(unsigned pi=0; pi<pend; pi++)
   {
      if(selected_lods[pi]!=lods.size())//object is not culled
      {
         //cerr<<"drawing "<<pi<<", LOD "<<selected_lods[pi]<<endl;
         glPushMatrix();

         const vect& to_origin = transl_to_origin[selected_lods[pi]];
         const vect& to_pos = positions[pi];

         
         glRotatef(vrot, vrotv.x, vrotv.y, vrotv.z);
         glRotatef(hrot, hrotv.x, hrotv.y, hrotv.z);
         //glTranslatef(0.f,0.f,-2.0f);
         glTranslatef(-user_pos.x, -user_pos.y, -user_pos.z);
         glTranslatef(to_pos.x, to_pos.y, to_pos.z);
         glScalef(0.3,0.3,0.3);
         glTranslatef(to_origin.x, to_origin.y, to_origin.z);
         //glColor3f(0.5,0.3,0.9);
         glColor3f(0.2+(pi%4)*0.3, 0.9-(pi%4)*0.2, (pi%4)*0.2);
         glutSolidCube(0.3);

         glBegin(GL_TRIANGLES);
         {
            unsigned o;
            if(this->use_lod)
               o = selected_lods[pi];
            else
               o = 0;

            int endj=lods[o].tcount;
            vertex* v;

            for(int j=0; j<endj; j++)
            {
               
               v = &(lods[o].vects)[(lods[o].tris)[j].verts[0]];
               glNormal3fv(v->normal);
               glVertex3fv(v->xyz);
               v = &(lods[o].vects)[(lods[o].tris)[j].verts[1]];
               glNormal3fv(v->normal);
               glVertex3fv(v->xyz);
               v = &(lods[o].vects)[(lods[o].tris)[j].verts[2]];
               glNormal3fv(v->normal);
               glVertex3fv(v->xyz);
            }
         }
         glEnd();

         glPopMatrix();
      }
      //else
         //cerr<<"not drawing "<<pi<<endl;
   }

   glPopMatrix();

#ifdef FRAME_COUNTER
   {
      double fps = G_fpsCounter.GetFPS();
      //if(G_prev_fps != fps)
      //{
         //G_prev_fps = fps;
         //printf("%.1f\n", fps);
         //cerr<<fps<<endl;
      //}
      
      char tab[32];
      PushOrtho(width, height); 
      sprintf(tab, "%.1f fps", fps);
      glColor3f(2.0,2.0,1.5);
      DrawString(tab, int((4.0/5.0)*width), 30);
      PopOrtho();
   }
#endif
}

inline float test_screen::distance3d(const vect& v, const vect& w)
{
   float f = (w.x-v.x)*(w.x-v.x) + (w.y-v.y)*(w.y-v.y) + (w.z-v.z)*(w.z-v.z);
   return std::pow(f,0.5f);
}

void test_screen::select_lods()
{
   unsigned pend = positions.size();
   for(unsigned pi=0; pi<pend; pi++)
   {
      float distance = distance3d(positions[pi], user_pos);
      unsigned end = rules.size();
      unsigned i=0; 
      for(; i<end; i++)
         if(distance < rules[i])
            break;
      selected_lods[pi] = i;
      // i == rules.size()  => object culled
   }
   //for(unsigned pi=0; pi<pend; pi++)
    //  cerr<<selected_lods[pi]<<endl;
}

void test_screen::find_ranges()
{
   unsigned lend=lods.size();
   ranges.resize(lend);
   for(unsigned li=0; li<lend; li++)
   {
      unsigned end = lods[li].vcount;
      assert(end==lods[li].vects.size());

      float& minx = ranges[li].first.x;
      float& maxx = ranges[li].second.x;
      float& miny = ranges[li].first.y;
      float& maxy = ranges[li].second.y;
      float& minz = ranges[li].first.z;
      float& maxz = ranges[li].second.z;

      minx = INFINITY_COORDINATE;// will store minimum
      maxx = INFINITY_COORDINATE;// and maximum
      maxx = -maxx;
      miny = INFINITY_COORDINATE;
      maxy = INFINITY_COORDINATE;
      maxy = -maxy;
      minz = INFINITY_COORDINATE;
      maxz = INFINITY_COORDINATE;
      maxz = -maxz;

      const std::vector<vertex>& vects = lods[li].vects;
      for(unsigned i=0; i<end; i++)
      {
         if(vects[i].xyz[0] < minx) // x
            minx = vects[i].xyz[0];
         if(vects[i].xyz[0] > maxx)
            maxx = vects[i].xyz[0];
         if(vects[i].xyz[1] < miny) // y
            miny = vects[i].xyz[1];
         if(vects[i].xyz[1] > maxy)
            maxy = vects[i].xyz[1];
         if(vects[i].xyz[2] < minz) // z
            minz = vects[i].xyz[2];
         if(vects[i].xyz[2] > maxz)
            maxz = vects[i].xyz[2];
      }
   }
}

void test_screen::find_transl_to_origin()
{
   if(ranges.size() <= 0)
      return;
   unsigned end=ranges.size();
   transl_to_origin.resize(end);
   for(unsigned i=0; i<end; i++)
   {
      const vect& min = ranges[i].first;
      const vect& max = ranges[i].second;
      // -(min.x + (max.x-min.x)/2)
      transl_to_origin[i].x = (min.x-max.x)/2 - min.x;
      transl_to_origin[i].y = (min.y-max.y)/2 - min.y;
      transl_to_origin[i].z = (min.z-max.z)/2 - min.z;
   }
}

void test_screen::change_polygon_mode()
{
   if(polygon_mode==GL_FILL)
      polygon_mode=GL_LINE;
   else
      polygon_mode=GL_FILL;
   glPolygonMode(GL_FRONT_AND_BACK, polygon_mode);
}
